#ifndef _UDP_SERVER_HPP
#define _UDP_SERVER_HPP

#include "Log.hpp"
#include <iostream>
#include <unordered_map>
#include <cstdio>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <queue>

#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _ip(ip),
          _port(port),
          _socketFd(-1)
    {
    }

    bool initServer()
    {
        // 新系统调用完成网络功能

        // 1. socket创建套接字 协议用IPv4 套接字类型用数据报套接字
        _socketFd = socket(AF_INET, SOCK_DGRAM, 0); // #define AF_INET PF_INET 二者相同
        if (_socketFd < 0)
        {
            logMsg(FATAL, "%d:%s", errno, strerror(errno));
            exit(2); // 正规写法：规定每个退出码代表什么意思
        }

        // 2. bind绑定端口号 将一个套接字绑定到一个特定的地址和端口
        // 将用户设置的ip和port在内核中和我们当前的进程强关联

        struct sockaddr_in svr_socketAddr;
        bzero(&svr_socketAddr, sizeof(svr_socketAddr));

        svr_socketAddr.sin_family = AF_INET;
        svr_socketAddr.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        svr_socketAddr.sin_port = htons(_port);

        // int bind(int __fd, const sockaddr *__addr, socklen_t __len)
        if (bind(_socketFd, (struct sockaddr *)&svr_socketAddr, sizeof(svr_socketAddr)) < 0)
        {
            logMsg(FATAL, "%d:%s", errno, strerror(errno));
            exit(2);
        }
        logMsg(NORMAL, "init udp server done ... %s", strerror(errno));
        return true;
    }

    void Start()
    {
        char buffer[SIZE];
        while (true)
        {
            // client_addr 纯输出型参数
            struct sockaddr_in client_addr;
            bzero(&client_addr, sizeof(client_addr));

            // clint_addrlen: 输入输出型参数
            socklen_t clint_addrlen = sizeof(client_addr); // unsigned int

            // 读取数据
            ssize_t bytes_read = recvfrom(_socketFd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client_addr, &clint_addrlen);
            if (bytes_read == -1)
            {
                logMsg(FATAL, "%d:%s", errno, strerror(errno));
                exit(3);
            }

            //一旦收到新用户的信息 就把新用户记录下来
            char client[64];
            if (bytes_read > 0)
            {
                buffer[bytes_read] = 0; // 数据当做字符串使用

                std::string cli_ip = inet_ntoa(client_addr.sin_addr); // 4字节的网络序列的IP->本主机字符串风格的IP
                uint16_t client_port = ntohs(client_addr.sin_port);   // 网络字节序 --> 主机字节序

                snprintf(client, sizeof(client), "%s-%u", cli_ip.c_str(), client_port); // 127.0.0.1-8080
                logMsg(NORMAL, "client login and send: %s", client);

                std::string client_str = client;
                auto it = _clients.find(client_str);
                if (it == _clients.end())
                {
                    logMsg(NORMAL, "add new client : %s", client);
                    _clients.insert({client_str, client_addr});
                }
            }
            
            //svr把一个客户端发送过来的信息发送给了他所记录的所有的client
            for (auto &iter : _clients)
            {
                std::string sendMsg = "\n";
                sendMsg += client;
                sendMsg += "# ";
                sendMsg += buffer; // 127.0.0.1-8080# 你好
                logMsg(NORMAL, "push message to %s", iter.first.c_str());
                sendto(_socketFd, sendMsg.c_str(), sendMsg.size(), 0, (struct sockaddr *)&(iter.second), sizeof(iter.second));
            }
        }
    }

    ~UdpServer()
    {
        if (_socketFd >= 0)
            close(_socketFd);
    }

private:
    // ip和port
    std::string _ip;
    uint16_t _port;
    int _socketFd;
    std::unordered_map<std::string, struct sockaddr_in> _clients;

    /*for test: PC模型
    多线程服务端: 解耦服务端 -- PC模型
    A线程只读数据且把数据推送到队列
    B线程只取数据且把数据发给客户端
        std::queue<std::string> msgQueue;
    如果消息是任务 还可以把任务交给线程池处理
    */
};

#endif
